package schuchert.agent;

import java.io.PrintStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.Properties;

import junit.framework.Assert;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import util.MockeryUtil;

@RunWith(JMock.class)
public class ConfigurableClassFileTransformerRedirectorTest {
    Mockery context = MockeryUtil.createMockeryForConcreteClasses();
    Instrumentation insrumentationSpy;
    PrintStream errorOutSpy;

    @Before
    public void createInsrumentationSpy() {
        insrumentationSpy = context.mock(Instrumentation.class);
    }

    @Before
    public void createAndRegisterErrorOutSpy() {
        errorOutSpy = context.mock(PrintStream.class);
        ConfigurableClassFileTransformerRedirector.ERROR_OUT = errorOutSpy;
    }

    @Before
    public void clearSystemProperty() {
        Properties systemProperties = System.getProperties();
        systemProperties.remove(ConfigurableClassFileTransformerRedirector.DEST_CLASS_PROPERTY_NAME);
        System.setProperties(systemProperties);
        Assert.assertNull(System.getProperty(ConfigurableClassFileTransformerRedirector.DEST_CLASS_PROPERTY_NAME));
    }

    @Test
    public void nullTransformerRegisteredAndErrorReportedWhenNoSystemPropertyDefined() {
        expectMissingSystemPropertyMessage();
        expectRegistryOfNullClassFileTransformer();
        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void systemPropertyDefinedButNullAlwaysPassesBecuaseNotPossibleToCreaetNullSystemProperty() {
    }

    @Test
    public void missingSystemPropertyReportedWhenSystemPropertyEmptyString() {
        expectMissingSystemPropertyMessage();
        expectRegistryOfNullClassFileTransformer();

        setSystemProperty("");
        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void missingSystemPropertyReportedWhenSystemPropertyContainsNothingButSpaces() {
        expectMissingSystemPropertyMessage();
        expectRegistryOfNullClassFileTransformer();

        setSystemProperty("   ");
        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void noErrorsWhenSystemPropertySetToSingleClassThatExistsInClassPath() {
        expectRegistryOfNullClassFileTransformer();

        setSystemProperty(NullClassFileTransformer.class.getName());

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void noErrorsWhenSystemPropertySetToSingleClassWithSpacesOnEndsThatExistsInClassPath() {
        expectRegistryOfNullClassFileTransformer();

        setSystemProperty("   " + NullClassFileTransformer.class.getName() + "   ");

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void noErrorsWhenSystemPropertySetToMultipleClassesAllOfWhichAreInClassPath() {
        expectRegistryOfNullClassFileTransformer();
        expectRegistryOf(ClassAndPackageNamePrintingClassFileTransformer.class);

        setSystemProperty(NullClassFileTransformer.class.getName() + ":"
                + ClassAndPackageNamePrintingClassFileTransformer.class.getName());

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void noErrorsWhenSystemPropertySetToMultipleClassesWithSpacesAtEndsOfNames() {
        expectRegistryOfNullClassFileTransformer();
        expectRegistryOf(ClassAndPackageNamePrintingClassFileTransformer.class);

        setSystemProperty(" " + NullClassFileTransformer.class.getName() + " : "
                + ClassAndPackageNamePrintingClassFileTransformer.class.getName() + " ");

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void nullTransformerRegisteredWhenSingleMissingClassInSystemProperty() {
        expectReportOfClassNotFoundAndNullTransformerRegistered();

        setSystemProperty("MissingClassThatShouldNotExistInClassPathEver");

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void nullTransformerRegisteredWhenSingleBadClassNameInSystemProperty() {
        expectReportOfClassNotFoundAndNullTransformerRegistered();

        setSystemProperty("InvalidClassName^^^^");

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    @Test
    public void nullTransformerRegisteredForEachBadClassWhenBothGoodAndBadClassNamesInSystemProperty() {
        expectRegistryOfNullClassFileTransformer();
        expectReportOfClassNotFoundAndNullTransformerRegistered();
        expectRegistryOf(ClassAndPackageNamePrintingClassFileTransformer.class);

        setSystemProperty(NullClassFileTransformer.class.getName() + ":InvalidClassname^^^:"
                + ClassAndPackageNamePrintingClassFileTransformer.class.getName());

        ConfigurableClassFileTransformerRedirector.premain("", insrumentationSpy);
    }

    private void setSystemProperty(String value) {
        System.setProperty(ConfigurableClassFileTransformerRedirector.DEST_CLASS_PROPERTY_NAME, value);
    }

    private void expectMissingSystemPropertyMessage() {
        context.checking(new Expectations() {
            {
                one(errorOutSpy).printf(
                        with(equal(ConfigurableClassFileTransformerRedirector.MISSING_SYSTEM_PROPERTY)),
                        with(any(String.class)));
                 one(errorOutSpy).printf(with(equal(ConfigurableClassFileTransformerRedirector.EXAMPLE_MESSAGE)),
                 with(any(Object.class)));
            }
        });
    }

    private void expectRegistryOfNullClassFileTransformer() {
        expectRegistryOf(NullClassFileTransformer.class);
    }

    private void expectRegistryOf(final Class<?> clazz) {
        context.checking(new Expectations() {
            {
                one(insrumentationSpy).addTransformer((ClassFileTransformer) with(any(clazz)));
            }
        });
    }

    private void expectClassNotFoundExceptionReported() {
        context.checking(new Expectations() {
            {
                one(errorOutSpy).println(with(any(ClassNotFoundException.class)));
                ignoring(errorOutSpy).println(with(any(String.class)));
            }
        });
    }

    private void expectReportOfMissingClass() {
        context.checking(new Expectations() {
            {
                one(errorOutSpy).printf(with(equal(ConfigurableClassFileTransformerRedirector.INSTANTIATION_ERROR)),
                        with(any(String.class)));
            }
        });
    }

    private void expectReportOfClassNotFoundAndNullTransformerRegistered() {
        expectClassNotFoundExceptionReported();
        expectReportOfMissingClass();
        expectMissingSystemPropertyMessage();
        expectRegistryOfNullClassFileTransformer();
    }
}
